package microcredit.db;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.derby.iapi.services.io.FileUtil;

public class Backup {

	private static final Log LOGGER = LogFactory.getLog(Backup.class);
	
	private static final String BACKUP_FOLDER = "./backup";
	private static final String MONTHLY_FOLDER = BACKUP_FOLDER+"/"+"monthly";
	
	/** An automatic backup will be performed each 7 days */
	private static final int AUTOMATIC_BACKUP_DELAY = 7;
	
	public static void automaticBackup() throws Exception {
		Backup backup = new Backup();
		
		BackupFile lastBackup = backup.getLastBackup();
		if (lastBackup == null) {
			// no backup found -> create backup
			forcedBackup();
		} else {
			Calendar c = Calendar.getInstance();
			c.setTime(lastBackup.getTime());
			c.add(Calendar.DAY_OF_YEAR, AUTOMATIC_BACKUP_DELAY);
			if (c.getTime().compareTo(new Date()) < 0) {
				// backup neccessary
				forcedBackup();
			} else {
				LOGGER.info("No backup necessary. Last backup was "+lastBackup.getTime());
			}
		}
	}
	public static void forcedBackup() throws Exception {
		Backup backup = new Backup();
		backup.runBackup(BackupFile.createNew(new File(BACKUP_FOLDER)));
	}
	
	public BackupFile getLastBackup() {
		List<BackupFile> files = readBackups();
		BackupFile foundBackup = null;
		for (BackupFile backupFile : files) {
			if (foundBackup == null) {
				foundBackup = backupFile;
			} else if (backupFile.getTime().compareTo(foundBackup.getTime()) > 0) {
				foundBackup = backupFile;
			}
		}
		return foundBackup;
	}
	
	public void runBackup(BackupFile f) throws Exception {
		LOGGER.info("Running automated backup: "+f.getFile().getAbsolutePath());
		try {
			
			f.getFile().getParentFile().mkdirs();
			f.getFile().createNewFile();
			Factory.close(false);
			Factory.createImportExport().exportDatabase(f.getFile());
			LOGGER.info("Backup created at: "+f.getFile().getAbsolutePath());
			
			archiveOldBackups();
			
		} finally {
			Factory.open();
		}
	}
	
	private void archiveOldBackups() {
		LOGGER.info("Archiving old backups");
		
		List<BackupFile> files = readBackups();
		List<BackupFile> toArchive = new ArrayList<BackupFile>();
		for (BackupFile backupFile : files) {
			Calendar c = Calendar.getInstance();
			c.setTime(backupFile.getTime());
			c.add(2, Calendar.MONTH);
			if (c.getTime().compareTo(new Date()) < 0) {
				// if older than 2 months
				toArchive.add(backupFile);
			}
		}
		if (toArchive.size() == 0) {
			return;
		}
		
		// group archive files by month
		Map<Date, List<BackupFile>> groups = new HashMap<Date, List<BackupFile>>();
		for (BackupFile backupFile : toArchive) {
			addByGroup(groups, backupFile);
		}
		List<BackupFile> archiveFiles = readExistingArchives();
		for (BackupFile backupFile : archiveFiles) {
			addByGroup(groups, backupFile);
		}
		
		writeArchive(groups);
	}
	
	private void writeArchive(Map<Date, List<BackupFile>> groups) {
		for (Date monthDate : groups.keySet()) {
			List<BackupFile> backupFiles = groups.get(monthDate);
			
			// oldest archive
			BackupFile oldest = null;
			for (BackupFile backupFile : backupFiles) {
				if (oldest == null) {
					oldest = backupFile;
				} else if (oldest.getTime().compareTo(backupFile.getTime()) < 0) {
					oldest = backupFile;
				}
			}
			
			// move backup file to monthly folder
			File monthlyFolder = createMonthlyFolder(oldest.getTime());
			monthlyFolder.mkdirs();
			
			boolean newlyCopied = false;
			// if already archived do nothing
			if (!monthlyFolder.equals(oldest.getFile().getParentFile())) {
				// copy file to new home
				newlyCopied = FileUtil.copyFile(oldest.getFile(), new File(monthlyFolder, oldest.getFile().getName()));
			}
			
			// delete all files from this month
			for (BackupFile backupFile : backupFiles) {
				if (backupFile == oldest && !newlyCopied) {
					// do not delete existing archives.
					continue;
				}
				boolean success = backupFile.getFile().delete();
				if (!success) {
					// currently we ignore this check
				}
			}
		}
	}
	
	private File createMonthlyFolder(Date time) {
		Calendar c = Calendar.getInstance();
		c.setTime(time);
		
		String year = c.get(Calendar.YEAR)+"";
		String month = c.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH);
		
		File file = new File(MONTHLY_FOLDER);
		
		return new File(file, year+"_"+month);
	}
	
	private void addByGroup(Map<Date, List<BackupFile>> groups,
			BackupFile backupFile) {
		Calendar c = Calendar.getInstance();
		c.setTime(backupFile.getTime());
		
		// reset time and day of month
		c.set(Calendar.HOUR_OF_DAY, 0);
		c.clear(Calendar.MINUTE);
		c.clear(Calendar.SECOND);
		c.clear(Calendar.MILLISECOND);
		c.set(Calendar.DAY_OF_MONTH, 1);
		
		Date monthTime = c.getTime();
		
		List<BackupFile> monthlyGroup = groups.get(monthTime);
		if (monthlyGroup == null) {
			monthlyGroup = new ArrayList<BackupFile>();
			groups.put(monthTime, monthlyGroup);
		}
		monthlyGroup.add(backupFile);
	}
	
	private List<BackupFile> readExistingArchives() {
		File monthlyFolder = new File(MONTHLY_FOLDER);
		List<BackupFile> backupFiles = new ArrayList<BackupFile>();
		if (!monthlyFolder.exists()) {
			return backupFiles;
		}
		
		File[] directories = monthlyFolder.listFiles(new DirectoryFileFilter());
		if (directories != null) {
			for (File directory : directories) {
				File[] files = directory.listFiles(new BackupFileFilter());
				if (files != null) {
					for (File file : files) {
						backupFiles.add(BackupFile.create(file));
					}
				}
			}
		}
		
		return backupFiles;
	}
	
	private List<BackupFile> readBackups() {
		List<BackupFile> backupFiles = new ArrayList<BackupFile>();
		File f = new File ("./backup");
		if (f.exists()) {
			File[] files = f.listFiles(new BackupFileFilter());
			
			if (files != null) {
				for (File file : files) {
					backupFiles.add(BackupFile.create(file));
				}
			}
		}
		return backupFiles;
	}
	
	
	private static class DirectoryFileFilter implements FileFilter {
		@Override
		public boolean accept(File pathname) {
			return pathname.isDirectory();
		}
	}
	
	private static class BackupFileFilter implements FileFilter {
		@Override
		public boolean accept(File pathname) {
			return BackupFile.isValidBackupFile(pathname);
		}
	}
	
	
}
